using System;
using System.Collections.Generic;
using System.Text;

namespace cb0tServer
{
    class UserPool
    {
        public static UserObject[] Users = new UserObject[700];

        private static Random rnd = new Random((int)cb0tServer.Helpers.UnixTime());

        /// <summary>
        /// Send a packet to ALL users currently logged in
        /// </summary>
        /// <param name="data">Ares Packet</param>
        public static void Broadcast(byte[] data)
        {
            foreach (UserObject user in Users)
            {
                if (user.IsSet())
                {
                    if (user.logged_in)
                    {
                        user.SendOutboundPacket(data);
                    }
                }
            }
        }

        public static void BroadcastScribble(ushort vroom, byte[] scribble)
        {
            List<byte> b = new List<byte>(scribble);

            if (b.Count <= 4000)
            {
                BroadcastScribblePacket(vroom, ServerOutboundPackets.CustomData(CoreSettings.room_bot, "cb0t_scribble_once", scribble));
            }
            else
            {
                List<byte[]> p = new List<byte[]>();

                while (b.Count > 4000)
                {
                    p.Add(b.GetRange(0, 4000).ToArray());
                    b.RemoveRange(0, 4000);
                }

                if (b.Count > 0)
                    p.Add(b.ToArray());

                for (int i = 0; i < p.Count; i++)
                {
                    if (i == 0)
                        BroadcastScribblePacket(vroom, ServerOutboundPackets.CustomData(CoreSettings.room_bot, "cb0t_scribble_first", scribble));
                    else if (i == (p.Count - 1))
                        BroadcastScribblePacket(vroom, ServerOutboundPackets.CustomData(CoreSettings.room_bot, "cb0t_scribble_last", scribble));
                    else
                        BroadcastScribblePacket(vroom, ServerOutboundPackets.CustomData(CoreSettings.room_bot, "cb0t_scribble_chunk", scribble));
                }
            }
        }

        /// <summary>
        /// Send a packet to users currently logged into a specific vroom
        /// </summary>
        /// <param name="vroom">Target Vroom</param>
        /// <param name="data">Ares Packet</param>
        public static void Broadcast(ushort vroom, byte[] data)
        {
            foreach (UserObject user in Users)
            {
                if (user.IsSet())
                {
                    if (user.logged_in)
                    {
                        if (user.vroom == vroom)
                        {
                            user.SendOutboundPacket(data);
                        }
                    }
                }
            }
        }

        private static void BroadcastScribblePacket(ushort vroom, byte[] packet)
        {
            foreach (UserObject user in Users)
                if (user.IsSet())
                    if (user.logged_in)
                        if (user.vroom == vroom)
                            if (!user.version.StartsWith("Ares"))
                                user.SendOutboundPacket(packet);
        }

        public static void BroadcastToVroomCustomEmotes(ushort vroom, byte[] data)
        {
            foreach (UserObject user in Users)
                if (user.IsSet())
                    if (user.logged_in)
                        if (user.vroom == vroom)
                            if (user.supports_custom_emotes)
                                user.SendOutboundPacket(data);
        }

        public static void SendCustomEmotes(UserObject userobj)
        {
            foreach (UserObject u in Users)
                if (u.IsSet())
                    if (userobj.logged_in)
                        if (u.vroom == userobj.vroom)
                            if (u.supports_custom_emotes)
                                foreach (CustomEmoteItem c in u.custom_emotes)
                                    userobj.SendOutboundPacket(ServerOutboundPackets.CustomEmoteItem(u, c));
        }

        /// <summary>
        /// Send a packet to unignored users currently logged into a specific vroom
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="data"></param>
        public static void BroadcastUnignored(UserObject sender, byte[] data)
        {
            foreach (UserObject user in Users)
            {
                if (user.IsSet())
                {
                    if (user.logged_in)
                    {
                        if (user.vroom == sender.vroom)
                        {
                            if (!user.ignores.Contains(sender.name))
                            {
                                user.SendOutboundPacket(data);
                            }
                        }
                    }
                }
            }
        }

        public static void BroadcastUnignoredCustomText(UserObject sender, String data, String org)
        {
            foreach (UserObject user in Users)
            {
                if (user.IsSet())
                {
                    if (user.logged_in)
                    {
                        if (user.vroom == sender.vroom)
                        {
                            if (!user.ignores.Contains(sender.name))
                            {
                                if (user.custom_tags.Contains("cb0t_no_custom_names"))
                                    user.SendOutboundPacket(ServerOutboundPackets.PublicTextPacket(sender.name, org));
                                else
                                    user.SendOutboundPacket(ServerOutboundPackets.AnnoucePacket(data));
                            }
                        }
                    }
                }
            }
        }

        public static void BroadcastCustomEmoticon(UserObject sender, byte[] data)
        {
            foreach (UserObject user in Users)
                if (user.IsSet())
                    if (user.logged_in)
                        if (user.vroom == sender.vroom)
                            if (user.custom_client)
                                user.SendOutboundPacket(data);
        }

        public static void BroadcastUnignored(String link_name, byte[] data)
        {
            foreach (UserObject user in Users)
            {
                if (user.IsSet())
                {
                    if (user.logged_in)
                    {
                        if (user.vroom == 0)
                        {
                            if (!user.ignores.Contains(link_name))
                            {
                                user.SendOutboundPacket(data);
                            }
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Send a packet to custom clients currently logged in
        /// </summary>
        /// <param name="sender">User to exclude</param>
        /// <param name="data">Ares Packet</param>
        public static void BroadcastCustomData(UserObject sender, byte[] data)
        {
            foreach (UserObject user in Users)
            {
                if (user.IsSet())
                {
                    if (user.logged_in)
                    {
                        if (!user.version.StartsWith("Ares "))
                        {
                            if (user.custom_client && (user.id != sender.id))
                            {
                                user.SendOutboundPacket(data);
                            }
                        }
                    }
                }
            }
        }

        public static void ShowWhoSupportsVC(UserObject userobj)
        {
            foreach (UserObject u in Users)
                if (u.IsSet())
                    if (u.logged_in)
                        if (u.vroom == userobj.vroom)
                            if (u.voice_chat || u.voice_private_chat)
                                userobj.SendOutboundPacket(ServerOutboundPackets.VoiceChatUserSupport(u));
        }

        public static void BroadcastVoiceClip(UserObject sender, byte[] packet)
        {
            foreach (UserObject u in Users)
                if (u.IsSet())
                    if (u.logged_in)
                        if (u.id != sender.id)
                            if (u.voice_chat)
                                if (!u.voice_chat_ignores.Contains(sender.name))
                                    u.SendOutboundPacket(packet);
        }

        /// <summary>
        /// Send a packet to ALL admins currently logged in
        /// </summary>
        /// <param name="data">Ares Packet</param>
        public static void BroadcastToAdmins(byte[] data)
        {
            foreach (UserObject user in Users)
            {
                if (user.IsSet())
                {
                    if (user.logged_in)
                    {
                        if (user.admin || user.host)
                        {
                            user.SendOutboundPacket(data);
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Send a packet to ALL hosts currently logged in
        /// </summary>
        /// <param name="data">Ares Packet</param>
        public static void BroadcastToHosts(byte[] data)
        {
            foreach (UserObject user in Users)
            {
                if (user.IsSet())
                {
                    if (user.logged_in)
                    {
                        if (user.host)
                        {
                            user.SendOutboundPacket(data);
                        }
                    }
                }
            }
        }

        public static void BroadcastToHosts(ushort vroom, byte[] data)
        {
            foreach (UserObject user in Users)
            {
                if (user.IsSet())
                {
                    if (user.logged_in)
                    {
                        if (user.host && user.vroom == vroom)
                        {
                            user.SendOutboundPacket(data);
                        }
                    }
                }
            }
        }

        public static void BroadcastVspy(ushort vroom, String text)
        {
            byte _level = CommandLevels.GetLevel(CommandLevels.LevelCodes.vspy);
            foreach (UserObject user in Users)
            {
                if (user.IsSet())
                {
                    if (user.logged_in)
                    {
                        if (user.vroom != vroom)
                        {
                            if (user.admin || user.host)
                            {
                                if (user.level >= _level)
                                {
                                    if (user.vspy)
                                    {
                                        user.SendOutboundPacket(ServerOutboundPackets.AnnoucePacket("14VSpy: " + text));
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }

        public static void RemoveIgnore(UserObject sender)
        {
            foreach (UserObject user in Users)
            {
                if (user.IsSet())
                {
                    if (user.logged_in)
                    {
                        if (user.vroom == sender.vroom)
                        {
                            user.ignores.Remove(sender.name);
                        }
                    }
                }
            }
        }

        public static void RemoveLinkIgnore(String sender)
        {
            foreach (UserObject user in Users)
            {
                if (user.IsSet())
                {
                    if (user.logged_in)
                    {
                        user.ignores.Remove(sender);
                    }
                }
            }
        }

        public static UserObject GetUserById(ushort id)
        {
            foreach (UserObject user in Users)
            {
                if (user.IsSet())
                {
                    if (user.logged_in)
                    {
                        if (user.id == id)
                        {
                            return user;
                        }
                    }
                }
            }

            return null;
        }

        public static UserObject GetUserByName(String name)
        {
            foreach (UserObject user in Users)
            {
                if (user.IsSet())
                {
                    if (user.logged_in)
                    {
                        if (user.name == name)
                        {
                            return user;
                        }
                    }
                }
            }

            return null;
        }

        public static UserObject GetVroom0UserByName(String name)
        {
            foreach (UserObject user in Users)
                if (user.IsSet())
                    if (user.logged_in)
                        if (user.name == name)
                            if (user.vroom == 0)
                                return user;

            return null;
        }

        public static UserObject GetUserByNameStartsWith(String name)
        {
            foreach (UserObject user in Users)
            {
                if (user.IsSet())
                {
                    if (user.logged_in)
                    {
                        if (user.name.StartsWith(name))
                        {
                            return user;
                        }
                    }
                }
            }

            return null;
        }

        public static int GetNextAvailableSocketId()
        {
            for (int x = 0; x < Users.Length; x++)
            {
                if (!Users[x].IsSet())
                {
                    return x;
                }
            }

            return -1;
        }

        public static void KillAllSessions()
        {
            foreach (UserObject user in Users)
            {
                if (user.IsSet())
                {
                    user.Disconnect();
                }
            }
        }

        public static bool NameExists(UserObject sender, String name)
        {
            foreach (UserObject user in Users)
            {
                if (user.IsSet())
                {
                    if (user.logged_in)
                    {
                        if (user.id != sender.id)
                        {
                            if (user.name == name) return true;
                            if (user.orgName == name) return true;
                        }
                    }
                }
            }

            return false;
        }

        public static int ClientCount(UserObject sender)
        {
            int count = 0;

            foreach (UserObject user in Users)
            {
                if (user.IsSet())
                {
                    if (user.logged_in)
                    {
                        if (user.physical_ip.Equals(sender.physical_ip))
                        {
                            count++;
                        }
                    }
                }
            }

            return count;
        }

        public static UserObject RandomUser(UserObject userobj)
        {
            ushort max = 0;

            foreach (UserObject u in Users)
                if (u.IsSet())
                    if (u.logged_in)
                        if (max < u.id)
                            max = u.id;

            int attempts = 0;

            while (true)
            {
                int i = (int)Math.Floor(rnd.NextDouble() * max);

                if (Users[i].IsSet())
                    if (Users[i].logged_in)
                        return Users[i];

                if (attempts++ > 20)
                    break;
            }

            return userobj;
        }

        public static UserObject RandomUser()
        {
            ushort max = 0;

            foreach (UserObject u in Users)
                if (u.IsSet())
                    if (u.logged_in)
                        if (max < u.id)
                            max = u.id;

            int attempts = 0;

            while (true)
            {
                int i = (int)Math.Floor(rnd.NextDouble() * max);

                if (Users[i].IsSet())
                    if (Users[i].logged_in)
                        return Users[i];

                if (attempts++ > 20)
                    break;
            }

            return null;
        }

        public static ushort UserCount()
        {
            ushort count = 1;

            foreach (UserObject user in Users)
            {
                if (user != null)
                {
                    if (user.IsSet())
                    {
                        if (user.logged_in)
                        {
                            count++;
                        }
                    }
                }
            }

            return count;
        }

        public static ushort UserCountForLinking()
        {
            ushort count = 0;

            foreach (UserObject user in Users)
                if (user.IsSet())
                    if (user.logged_in)
                        if (user.vroom == 0)
                            count++;

            return count;
        }

        public static UserObject FindGhost(UserObject sender)
        {
            foreach (UserObject user in Users)
            {
                if (user.IsSet())
                {
                    if (user.logged_in)
                    {
                        if (user.id != sender.id)
                        {
                            if (user.name == sender.name || user.orgName == sender.name)
                            {
                                if (user.guid.Equals(sender.guid) || user.physical_ip.Equals(sender.physical_ip))
                                {
                                    return user;
                                }
                            }
                        }
                    }
                }
            }

            return null;
        }

        public static void RecheckPoolForHosts()
        {
            foreach (UserObject u in Users)
            {
                if (u.IsSet())
                {
                    if (u.logged_in)
                    {
                        if (u.physical_ip.Equals(CoreSettings.additional_host)) continue;

                        if (u.host)
                        {
                            u.physical_ip = CoreSettings.room_external_ip;
                        }
                        else
                        {
                            if (u.physical_ip.Equals(CoreSettings.room_external_ip))
                            {
                                u.admin = true;
                                u.host = true;
                                u.level = 5;
                                Broadcast(u.vroom, ServerOutboundPackets.UpdatePacket(u));
                                u.SendOutboundPacket(ServerOutboundPackets.OpChangePacket(u));
                                SCASPool.Broadcast(SCASOutboundPackets.UpdateUserItem(u));
                            }
                        }
                    }
                }
            }
        }

        public static byte[] GetSuperNodes()
        {
            List<ushort> available_ids = new List<ushort>();

            foreach (UserObject u in Users)
                if (u.IsSet())
                    if (u.logged_in)
                        if (u.version.Length == 15)
                            if (u.version.StartsWith("Ares") && u.version[10].ToString() == ".") // genuine ares client
                                if (!u.node_ip.Equals(System.Net.IPAddress.Parse("0.0.0.0")))
                                    if (u.node_port != 0)
                                        available_ids.Add(u.id);

            if (available_ids.Count == 0) // unlikely, but no nodes available :-(
                return null;

            if (available_ids.Count > 20) // trim
            {
                Random rnd = new Random((int)cb0tServer.Helpers.UnixTime());

                while (available_ids.Count > 20)
                    available_ids.RemoveAt((int)Math.Floor(rnd.NextDouble() * available_ids.Count));
            }

            List<byte> supernodes = new List<byte>();

            foreach (ushort i in available_ids)
            {
                supernodes.AddRange(Users[i].node_ip.GetAddressBytes());
                supernodes.AddRange(BitConverter.GetBytes(Users[i].node_port));
            }

            return supernodes.ToArray();
        }

        public static void BroadcastPingReply(UserObject userobj, long reply_time)
        {
            PingUserObject p = new PingUserObject();
            p.name = userobj.name;
            p.timestamp = 0;

            foreach (UserObject user in Users)
            {
                if (user.IsSet())
                {
                    if (user.logged_in)
                    {
                        for (int i = 0; i < user.pings.Count; i++)
                        {
                            if (user.pings[i].Equals(p))
                            {
                                long result = reply_time - user.pings[i].timestamp;
                                user.pings.RemoveAt(i);
                                user.SendOutboundPacket(ServerOutboundPackets.AnnoucePacket("\x000314" + userobj.name + " ping replied in " + result + " ms"));
                                return;
                            }
                        }
                    }
                }
            }
        }



    }
}
